<script setup>
import { ref, computed, provide, inject, nextTick } from 'vue'
import { defineEmits, defineProps, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import chatApi from '@/apis/chat'

const emit = defineEmits([
  'switchChannel',
  'renameChat',
  'deleteChat',
  'favoriteChat',
  'clearHistory',
  'switchStyle',
])

const props = defineProps({
  objectType: String,
  objectId: Number,
  chatInfo: Object,
  channels: Array,
  channel: Object,
  displayStyles: {
    type: Array,
    default: ['bubble', 'list'],
  },
  defaultDisplayStyle: {
    type: String,
    default: 'list',
  },
  showClearBtn: Boolean,
  showDeleteBtn: Boolean,
  showRenameBtn: Boolean,
  showStyleBtn: Boolean,
  showFavoriteBtn: Boolean,
})

const showLogo = inject('showLogo')
const isSmallPage = inject('isSmallPage')

const displayStyle = ref(props.defaultDisplayStyle || props.displayStyles[0] || 'list')

const promptText = ref('') //输入框默认内容
const newMessage = ref(null) //新数据
const ctrl = new AbortController() //用于中断接收
const stopReceive = ref(false)

//消息列表组件ref
const messagesRef = ref(null)
//输入框ref
const inputRef = ref(null)

const uniqueKey = computed(() => `${props.objectType}|${props.objectId}|${props.channel?.id}`)

//显示停止按钮
const showStopBtn = computed(() => {
  return newMessage?.value?.receive?.status === 'receiving'
})

const optButtons = computed(() => [
  {
    label: '显示模式',
    icon: { icon: displayStyle.value === 'list' ? 'list' : 'comments' },
    command: 'style',
    show: props.showStyleBtn,
  },
  {
    label: props.chatInfo.favorite ? '取消收藏' : '收藏',
    icon: {
      icon: 'star',
      class: props.chatInfo.favorite ? 'text-[--el-color-warning]' : '',
    },
    command: 'favorite',
    show: props.showFavoriteBtn,
  },
  {
    label: '重命名',
    icon: { icon: 'pen-square' },
    command: 'rename',
    show: props.showRenameBtn,
  },
  {
    label: '清空记录',
    icon: { icon: 'broom' },
    command: 'clear',
    show: props.showClearBtn,
  },
  {
    label: '删除对话',
    icon: { icon: 'trash-alt' },
    command: 'delete',
    show: props.showDeleteBtn,
  },
])

const initialized = computed(() => {
  return props.objectId && props.channel?.id !== undefined
})
/**
 * 操作按钮
 * @param command 命令
 */
const optsCommand = (command) => {
  switch (command) {
    case 'style':
      switchDisplayStyle()
      break
    case 'favorite':
    case 'rename':
    case 'delete':
      emit(command + 'Chat')
      break
    case 'clear':
      ElMessageBox.confirm('确定要清空所有对话内容吗？', '提示', { type: 'warning' })
        .then(() => clearHistory())
        .catch((err) => {
          console.log(err)
        })
      break
  }
}

//切换显示模式
const switchDisplayStyle = () => {
  const i = props.displayStyles.findIndex((item) => item === displayStyle.value)
  // 按顺序循环切换
  const i2 = (i + 1) % props.displayStyles.length
  displayStyle.value = props.displayStyles[i2]
  emit('switchStyle', displayStyle.value)
}

/**
 * 加载历史记录
 */
const loadMessages = async (startId = 0, pageSize = 10) => {
  const { data: paginator } = await chatApi.message.histories(props.objectType, props.objectId, {
    channelId: props.channel?.id,
    lastId: startId,
    pageSize: pageSize,
  })
  return paginator
}

//切换通道
const switchChannel = (newId) => {
  const tmp = props.channels.find((c) => c.id === newId)
  if (tmp) {
    if (tmp.id !== props.channel.id) {
      //channel.value = tmp
      emit('switchChannel', tmp)
      ElMessage.success(`通道已切换为：${tmp.name}`)
    }
  } else {
    ElMessage.error('通道不存在')
  }
}

//清空历史记录
const clearHistory = async () => {
  await chatApi.message.deleteAll(props.objectType, props.objectId, props.channel.id)
  if (messagesRef.value) {
    messagesRef.value.initData(true)
  }
  ElMessage.success('历史记录已清空')
}

//清空上下文
const clearContext = async () => {
  if (!messagesRef.value) return

  const lastMessage = messagesRef.value.lastMessage ?? null
  if (lastMessage?.prompt?.next_anew === 0) {
    await chatApi.message.clearContext(lastMessage.id)
    messagesRef.value.clearContext()
  } else {
    ElMessage.warning('上下文已清空')
  }
}

//发送消息
const sendMessage = async (content) => {
  if (newMessage.value) {
    ElMessage.warning('正在接收上一条消息，请稍后发送。')
    return
  }
  //发送
  const { objectType, objectId } = props
  const options = {
    isFirst: messagesRef.lastMessage?.prompt.next_anew ? 1 : 0,
  }
  const { data } = await chatApi.message.send(
    objectType,
    objectId,
    props.channel.id,
    content,
    options
  )
  //清空输入框
  promptText.value = ''
  //新消息内容初始化
  newMessage.value = {
    id: data.id,
    prompt: data,
    receive: { status: 'waiting', content: '' },
  }
  //滚动页面
  nextTick(() => messagesRef.value?.scrollToBottom())
  //接收
  streamReceiveMessage(data.id)
}

/**
 * 接收消息
 * @param receiveId 接受消息的id
 */
const receiveMessage = async (receiveId) => {
  try {
    const { data } = await chatApi.message.receive(receiveId)
    console.log('receiveMessage result', data)
    receive.value = data
  } catch (error) {
    receive.value = {
      message: { content: '啊哦，出错了！', role: 'system' },
      status: 'error',
      time: new Date().toLocaleString(),
    }
    console.log('receiveMessage error', error)
  }
}

/**
 * 流式接收消息
 * @param promptId 输入数据
 */
const streamReceiveMessage = async (promptId) => {
  let content = ''
  stopReceive.value = false
  chatApi.message
    .streamReceive(
      promptId,
      //接收消息
      (data) => {
        content += data.content
        if (data.status === 'error' && data.clear === true) {
          content = data.content ?? '我很抱歉，我无法回答您的这个问题。'
        }
        data.content = content
        newMessage.value.receive = data
      },
      //结束
      () => {
        console.log('streamReceiveMessage-end')
      },
      ctrl
    )
    .catch((err) => {
      console.error(err)
      if (err.toString().indexOf('401') > 0) {
        newMessage.value = ''
      } else {
        newMessage.value.receive = {
          content: '请求失败，请稍后尝试。',
          status: 'error',
        }
      }
    })
}

/**
 * 中断接收
 */
const stopReceiveMessage = async () => {
  if (newMessage.value?.id) {
    stopReceive.value = true
    chatApi.message.cancel(newMessage.value.id)
  }
}

/**
 * 重答
 * @param prompt 输入数据
 */
const replyAgainMessage = async (prompt) => {
  sendMessage(prompt.prompt_text)
}

//插入示例Prompt
const sendPromptSample = (text) => {
  // promptText.value = text
  // inputRef.value.focus()
  sendMessage(text)
}

onMounted(() => {
  showLogo.value = !isSmallPage.value
})

onUnmounted(() => {
  showLogo.value = true
})

provide('sendPromptSample', sendPromptSample)
provide('clearContext', clearContext)
</script>

<template>
  <div class="relative flex flex-col h-full w-full">
    <div
      class="flex items-center justify-between px-3 sm:px-4 py-2 border-b border-[--el-border-color] bg-[--el-bg-color]"
    >
      <div class="title min-w-0 flex-shrink">
        <slot name="title">
          <div class="flex items-center space-x-2">
            <img
              :src="props.chatInfo.icon"
              class="w-10 h-10 rounded-full"
              v-if="props.chatInfo?.icon"
            />
            <div class="flex flex-col truncate">
              <span class="font-bold text-[--el-text-color-primary]">
                {{ props.chatInfo?.title ?? '&nbsp;' }}
              </span>
              <span class="text-xs text-[--el-text-color-secondary] flex items-center space-x-0.5">
                <FontAwesomeIcon icon="cubes" />
                <span class="text-[--el-text-color-secondary]">{{ props.channel?.name }}</span>
                <span>({{ props.channel?.consumption_score }}积分/次)</span>
              </span>
            </div>
          </div>
        </slot>
      </div>
      <div class="buttons flex-shrink-0">
        <slot name="buttons">
          <div class="flex justify-end space-x-1.5">
            <el-dropdown
              v-if="props.channel && channels.length > 1"
              style="outline: none"
              @command="switchChannel"
            >
              <el-button type="primary" plain :style="{ width: isSmallPage ? '32px' : 'auto' }">
                <FontAwesomeIcon icon="cubes" class="sm:mr-1" />
                <span v-if="!isSmallPage">{{ props.channel?.name }}</span>
                <FontAwesomeIcon icon="angle-down" class="ml-2" v-if="!isSmallPage" />
              </el-button>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item
                    v-for="item in $props.channels"
                    :key="item.id"
                    :command="item.id"
                  >
                    {{ item.name }}
                    <FontAwesomeIcon
                      icon="arrow-left"
                      v-if="item.id === props.channel.id"
                      class="ml-1 text-[--el-color-primary]"
                    />
                  </el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
            <el-dropdown @command="optsCommand" v-if="isSmallPage">
              <el-button type="default" plain style="width: 32px">
                <FontAwesomeIcon icon="list" />
              </el-button>
              <template #dropdown>
                <el-dropdown-menu>
                  <template v-for="btn in optButtons">
                    <el-dropdown-item :command="btn.command" v-if="btn.show">
                      {{ btn.label }}
                    </el-dropdown-item>
                  </template>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
            <el-button-group v-if="!isSmallPage">
              <template v-for="btn in optButtons">
                <el-button
                  v-if="btn.show"
                  style="width: 32px"
                  :title="btn.label"
                  @click="optsCommand(btn.command)"
                >
                  <FontAwesomeIcon v-bind="btn.icon" />
                </el-button>
              </template>
            </el-button-group>
          </div>
        </slot>
      </div>
    </div>
    <ChatMessageBox
      v-if="initialized"
      ref="messagesRef"
      v-bind="$attrs"
      v-model:message="newMessage"
      :unique-key="uniqueKey"
      :load="loadMessages"
      :display-style="displayStyle"
      @reply-again="replyAgainMessage"
    >
      <template #header></template>
      <template #footer>
        <!-- 浮动在上层div的底部，居中按钮 -->
        <div class="stop-container" v-if="showStopBtn && !isSmallPage">
          <el-button type="primary" round @click="stopReceiveMessage">
            <FontAwesomeIcon icon="stop" />
            <span class="pl-1">停止</span>
          </el-button>
        </div>
      </template>
    </ChatMessageBox>
    <ChatInput
      v-if="initialized"
      ref="inputRef"
      v-model="promptText"
      :placeholder="$attrs['prompt-placeholder']"
      :show-context-btn="$attrs['show-context-btn'] ?? false"
      :show-stop-btn="showStopBtn"
      @clear-context="clearContext"
      @send="sendMessage"
      @stop="stopReceiveMessage"
    >
    </ChatInput>
  </div>
</template>

<style scoped>
.stop-container {
  @apply sticky flex bottom-3 left-0 justify-center sm:justify-end pointer-events-none;
}

.stop-container .el-button {
  @apply pointer-events-auto shadow-md sm:mr-2;
}

:deep(.el-tooltip__trigger) {
  outline: unset;
}
</style>
